#include "dicom/handler/file_op_handler.h"

#include <boost/bind.hpp>
#include <gwbase/base/util.h>
#include <gwbase/base/logging.h>
#include <surbasic/surbasic.h>
#include <json/json.h>
#include "dicom/web_server.h"
#include "dicom/session.h"
#include "dicom/dmv_db.h"
#include "dicom/handler/handler_common.h"
#include "dicom/sync_manager.h"
#include "dicom/upload/cloud_uploader.h"

namespace dicom {

static HttpHandler* CreateFileOpHander(onion_request* conn) {
  return new FileOpHandler(conn);
}

void FileOpHandler::RegisterHandler(WebServer* s) {
  s->AddHandler("/api/file/delete", "DELETE",
                boost::bind(CreateFileOpHander, _1));

  s->AddHandler("/api/file/rename", "GET", boost::bind(CreateFileOpHander, _1));
}

int FileOpHandler::HandleRequest(onion_request* conn, onion_response* resp) {
  std::string url = GetReqPath(conn);
  if (url == "/api/file/delete") {
    return HandleDeleteFile(conn, resp);
  } else if (url == "/api/file/rename") {
    return HandleRenameFile(conn, resp);
  }

  return OCS_PROCESSED;
}

int FileOpHandler::HandleDeleteFile(onion_request* conn, onion_response* resp) {
  Status s;
  CredentialPtr c;

  s = CheckToken(conn, &c);
  if (!s.ok()) {
    SendSimpleResponse(resp, 401, "oauth error");
    return OCS_PROCESSED;
  }

  const std::string id = GetQueryString(conn, "id");
  if (id.empty()) {
    SendSimpleResponse(resp, 400, "id is empty");
    return OCS_PROCESSED;
  }

  DmvDBPtr db = GetGateway()->dmv_mgr()->GetDB(c->email);
  // Lock id, so nobody can change it when we delete it
  EntryLockGuard entry_lock2(db, id, ".");
  FileObject old;
  if (db->GetFileByID(id, &old) == false) {
    SendSimpleResponse(resp, 404, "file not found");
    return OCS_PROCESSED;
  }

  LogHippaOperation(OP_DELETE_FILE, db, c, old.parentId, old.name);

  SDBasic::TreeEntity node;
  if (!old.cloud_id.empty()) {
    bool result = SDBasic::DeleteFile(c->surdoc_key, old.cloud_id, node);
    if (!result) {
      SendSimpleResponse(resp, 500, "failed to delete file on surcloud");
      return OCS_PROCESSED;
    }
  }

  // cleanup
  db->DeleteFile(id);
  GetGateway()->cache()->Erase(old.local_id);
  GetGateway()->sync_mgr()->DeleteTask(old.local_id);

  db->UpdateParentDirModifyTime(old.parentId);

  Json::Value v;
  v["code"] = 0;
  v["msg"] = "success";
  SendJsonResult(resp, 200, v);
  return OCS_PROCESSED;
}

int FileOpHandler::HandleRenameFile(onion_request* conn, onion_response* resp) {
  Status s;
  CredentialPtr c;

  s = CheckToken(conn, &c);
  if (!s.ok()) {
    SendSimpleResponse(resp, 401, "oauth error");
    return OCS_PROCESSED;
  }

  const std::string id = GetQueryString(conn, "id");
  const std::string name = GetQueryString(conn, "name");
  const std::string pid = GetQueryString(conn, "pid");
  if (id.empty() || name.empty() || pid.empty()) {
    SendSimpleResponse(resp, 400, "id, name or pid is empty");
    return OCS_PROCESSED;
  }

  DmvDBPtr db = GetGateway()->dmv_mgr()->GetDB(c->email);
  EntryLockGuard entry_lock2(db, id, ".");
  EntryLockGuard entry_lock(db, pid, name);
  FileObject old;
  if (db->GetFileByID(id, &old) == false) {
    SendSimpleResponse(resp, 404, "file not found");
    return OCS_PROCESSED;
  }

  // Nothing to do
  if (old.parentId == pid && old.name == name) {
    Json::Value v;
    v["code"] = 0;
    v["msg"] = "success";
    Json::Value item;
    old.ToJson(item);
    v["item"] = item;
    SendJsonResult(resp, 200, v);
    return OCS_PROCESSED;
  }

  DirObject d;
  if (db->GetDirByID(pid, &d) == false) {
    SendSimpleResponse(resp, 404, "folder not found");
    return OCS_PROCESSED;
  }

  LogHippaOperation(OP_RENAME_FILE, db, c, old.parentId, old.name, pid, name);
  // Check whether there is a file with same name
  FileObject query;
  if (db->GetSubFileByName(pid, name, &query) == true) {
    if (!query.cloud_id.empty()) {
      SDBasic::TreeEntity node;
      bool result = SDBasic::DeleteFile(c->surdoc_key, query.cloud_id, node);
      if (!result) {
        SendSimpleResponse(resp, 500, "failed to delete file on surcloud");
        return OCS_PROCESSED;
      }
    }

    // cleanup
    db->DeleteFile(query.local_id);
    GetGateway()->cache()->Erase(query.local_id);
    GetGateway()->sync_mgr()->DeleteTask(query.local_id);
  }

  bool same_folder = (pid == old.parentId);
  if (same_folder) {
    // Rename file in same folder
    if (!old.cloud_id.empty()) {
      SDBasic::TreeEntity node;
      bool result =
          SDBasic::RenameFile(c->surdoc_key, old.cloud_id, name, node);
      if (!result) {
        SendSimpleResponse(resp, 500, "failed to rename file on surcloud");
        return OCS_PROCESSED;
      }
    }
    old.name = name;
    old.parentId = pid;
    db->UpdateFile(old);
    db->UpdateParentDirModifyTime(pid);

    Json::Value v;
    v["code"] = 0;
    v["msg"] = "success";
    Json::Value item;
    old.ToJson(item);
    v["item"] = item;
    SendJsonResult(resp, 200, v);
    return OCS_PROCESSED;

  } else {
    // This is a file moving(and renaming) operation
    DirObject d;
    if (db->GetDirByID(pid, &d) == false) {
      SendSimpleResponse(resp, 404, "destination folder not found");
      return OCS_PROCESSED;
    }

    // If file is already uploaded to cloud
    if (!old.cloud_id.empty()) {
      // Ensure destination dir is created on cloud
      if (d.cloud_id.empty()) {
        std::string dest_cloud_id;
        s = CloudUploader::CreateDirInCloud(c->surdoc_key, d.local_id, db,
                                            &dest_cloud_id);
        if (!s.ok()) {
          SendSimpleResponse(resp, 500,
                             "failed to create dest dir on surcloud");
          return OCS_PROCESSED;
        }
        d.cloud_id = dest_cloud_id;
      }

      // Do moving on cloud
      SDBasic::TreeEntity node;
      bool result =
          SDBasic::MoveFile(c->surdoc_key, old.cloud_id, d.cloud_id, node);
      if (!result) {
        SendSimpleResponse(resp, 500, "failed to move file on surcloud");
        return OCS_PROCESSED;
      }

      // Do renaming on cloud
      if (old.name != name) {
        SDBasic::TreeEntity node;
        bool result =
            SDBasic::RenameFile(c->surdoc_key, old.cloud_id, name, node);
        if (!result) {
          SendSimpleResponse(resp, 500, "failed to rename file on surcloud");
          return OCS_PROCESSED;
        }
      }
    }

    db->UpdateParentDirModifyTime(old.parentId);

    old.name = name;
    old.parentId = pid;
    db->UpdateFile(old);
    db->UpdateParentDirModifyTime(pid);

    Json::Value v;
    v["code"] = 0;
    v["msg"] = "success";
    Json::Value item;
    old.ToJson(item);
    v["item"] = item;
    SendJsonResult(resp, 200, v);

    return OCS_PROCESSED;
  }
}
}
